using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using Nini.Config;

namespace AutoFillGapsUI {
    public partial class mainForm : Form {
        string[] tracks = null;
        string iniFile;

        public mainForm(string file, string trackList) {
            InitializeComponent();
            this.FormClosing += delegate { exitApplication(false, false); };
            try {
                tracks = trackList != null && trackList.Length > 0 ? System.Text.Encoding.ASCII.GetString(System.Convert.FromBase64String(trackList)).Split('|') : null;
            }
            catch (Exception e) { }
            if (tracks != null && tracks.Length > 0) tracksContainingTransientsListBox.Items.AddRange(tracks);
            if (file == null || file.Length == 0 || !File.Exists(file)) exitApplication(false, false);
            iniFile = file;
            ReadIniFileAndSetInitialValues();
            SetToolTips();
        }

        private void SetValues(int itemsToProcess, int shiftLeftGapMsec, int gapFillCrossfadeMsec, int crossfadeType, int maxGapMsec, double minRate, bool groupHandling, bool takeCareOfTransients, int transientLengthMinMsec, int transientLengthMaxMsec, int transientSplitCrossfadeMsec, bool shiftRightGapToTransient, int maxGapToTransientLengthMsec, int gapToTransientPadMsec, string tracksContainingTransients, bool warnOnGroupMismatch, bool warnOnTransientStretched, bool warnOnLimitReached) {
            if (itemsToProcess > 0)
                itemsToProcessCombo.SelectedIndex = itemsToProcess - 1;
            shiftLeftGapMsecNumeric.Value = shiftLeftGapMsec;
            gapFillCrossfadeMsecNumeric.Value = gapFillCrossfadeMsec;
            crossfadeTypeCombo.SelectedIndex = crossfadeType;
            maxGapMsecNumeric.Value = maxGapMsec;
            minRateTrackBar.Value = valueToRateTrackbar(minRate);
            groupHandlingCheckBox.Checked = groupHandling;
            takeCareOfTransientsCheckBox.Checked = takeCareOfTransients;
            transientLengthMinTrackBar.Value = valueToTransientLengthTrackbar(transientLengthMinMsec);
            transientLengthMaxTrackBar.Value = valueToTransientLengthTrackbar(transientLengthMaxMsec);
            transientSplitCrossfadeMsecNumeric.Value = transientSplitCrossfadeMsec;
            shiftRightGapToTransientCheckBox.Checked = shiftRightGapToTransient;
            maxGapToTransientLengthMsecNumeric.Value = maxGapToTransientLengthMsec;
            gapToTransientPadMsecNumeric.Value = gapToTransientPadMsec;
            if (tracks != null) {
                if (tracksContainingTransients != null && tracksContainingTransients.Length > 0) {
                    string[] toCheck = tracksContainingTransients.Split('|');
                    for (int i = 0; i < toCheck.Length; ++i) {
                        int idx;
                        if (Int32.TryParse(toCheck[i], out idx) && idx >= 1 && idx <= tracks.Length)
                            tracksContainingTransientsListBox.SetItemChecked(idx - 1, true);
                    }
                }
                else {
                    for (int i = 0; i < tracks.Length; ++i)
                        tracksContainingTransientsListBox.SetItemChecked(i, true);
                }
            }

            groupMismatchCheckBox.Checked = warnOnGroupMismatch;
            transientStretchedCheckBox.Checked = warnOnTransientStretched;
            limitReachedCheckBox.Checked = warnOnLimitReached;

            minRateValue.Text = String.Format("{0:0.00}", valueFromRateTrackbar(minRateTrackBar.Value));
            showTransientLengthTooltip();

            takeCareOfTransientsPanel.Visible = takeCareOfTransients;
            shiftRightGapToTransientPanel.Visible = shiftRightGapToTransient;
        }

        private void SetToolTips() {
            new MyToolTip().SetToolTip(itemsToProcessLbl, "The media items to be looked up for gaps between them.");

            // general
            new MyToolTip().SetToolTip(shiftLeftGapMsecLbl, "The gap is first shifted to the left by the specified amount of time in msec.\nThis is useful in cases where the start of the right-hand item may fall on the middle of a transient\n(e.g. when splitting just before a snare transient but a cymbal hit is just a few msec before this),\nso shifting the gap slightly to the left helps recover the whole transient.");
            new MyToolTip().SetToolTip(gapFillCrossfadeMsecLbl, "Crossfade length when filling gaps in msec.");
            new MyToolTip().SetToolTip(crossfadeTypeLbl, "Crossfade type to use.");
            new MyToolTip().SetToolTip(maxGapMsecLbl, "Maximum amount of gap in msec that is allowed to be filled by stretching the left-hand item.\nIf this limit is reached, the rest of the gap will be filled using backfill from the right-hand item.");
            new MyToolTip().SetToolTip(minRateLbl, "Minimum rate that is allowed for stretching. If this limit is reached,\nthe rest of the gap will be filled using backfill from the right-hand item.");
            new MyToolTip().SetToolTip(groupHandlingLbl, "If enabled, all items in the same group will be handled in the same way (same split points,\nsame stretch factor) and transient detection will be performed in the whole group at once.\nIt's especially useful for drum editing when detecting the last transient in a group,\ne.g. if there's a kick followed by a snare in a group of items that has a gap afterwards,\nthe items will be splitted after the snare, thus avoiding stretching of both transients."); 

            // transient
            new MyToolTip().SetToolTip(takeCareOfTransientsLbl, "If enabled, will try to leave transients intact by splitting after\nthe transient and stretching the rest of the item only.");
            new MyToolTip().SetToolTip(transientLengthLbl, "Minimum & maximum length of a transient in msec. The item is splitted\nat a point after the transient where the stretch rate does not reach\nthe limit specified, while trying to keep as much as possible from the\ntransient based on the min/max range.");
            new MyToolTip().SetToolTip(transientSplitCrossfadeMsecLbl, "Crossfade length when splitting after transient in msec.");
            new MyToolTip().SetToolTip(shiftRightGapToTransientLbl, "If enabled, will try to shift right the gap just before the next transient.\nThis is useful if you were manually chopping the items but not exactly\nbefore the transients, so this can help to move the gap where it\nshould be, thus allowing less stretching afterwards.");
            new MyToolTip().SetToolTip(maxGapToTransientLengthMsecLbl, "The gap will be shifted to the right only if the space between\nthe gap and the next transient is no more than this amount\nof time in msec.");
            new MyToolTip().SetToolTip(gapToTransientPadMsecLbl, "Safety padding when shifting right the gap. It positions the gap\nthe specified amount of time in msec before the transient, because\nREAPER does not always detects the transients at their very start point.");
            new MyToolTip().SetToolTip(tracksContainingTransientsLbl, "Select the tracks that should be used for transient detection when\ngroup handling is enabled. This is useful e.g. for drum editing so that\nyou can choose for transient detection only kick/snare/tom tracks\nand ignoring overhead/room tracks."); 

            // error handling
            new MyToolTip().SetToolTip(groupMismatchLbl, "Place a marker at the end of a group that its items do not have\nthe same start or end position (and group handling is enabled).\nThe gap that follows will not be processed at all in this case.");
            new MyToolTip().SetToolTip(transientStretchedLbl, "Place a marker on a transient that was stretched (although\n'avoid to stretch transients' was enabled) because the\nmaximum gap length or minimum rate limit was reached,\neven using the minimum transient length, thus affecting\nprobably how it sounds.");
            new MyToolTip().SetToolTip(limitReachedLbl, "Place a marker on a gap where the maximum gap length or minimum\nrate limit was reached and in order to fill the whole gap, backfill\nfrom the right-hand item was used, thus leaving a possible glitch."); 
        }

        private void minRateTrackBar_Scroll(object sender, EventArgs e) {
            minRateValue.Text = String.Format("{0:0.00}", valueFromRateTrackbar(minRateTrackBar.Value));
        }

        // from trackbar value to real value
        private double valueFromRateTrackbar(int value) {
            return value / 100.0;
        }

        // from real value to trackbar value
        private int valueToRateTrackbar(double value) {
            return (int)Math.Round(value * 100.0);
        }

        private void transientLengthMinTrackBar_Scroll(object sender, EventArgs e) {
            if (transientLengthMinTrackBar.Value > transientLengthMaxTrackBar.Value)
                transientLengthMaxTrackBar.Value = transientLengthMinTrackBar.Value;
            showTransientLengthTooltip();
        }

        private void transientLengthMaxTrackBar_Scroll(object sender, EventArgs e) {
            if (transientLengthMaxTrackBar.Value < transientLengthMinTrackBar.Value)
                transientLengthMinTrackBar.Value = transientLengthMaxTrackBar.Value;
            showTransientLengthTooltip();
        }

        private void showTransientLengthTooltip() {
            int minValue = valueFromTransientLengthTrackbar(transientLengthMinTrackBar.Value);
            int maxValue = valueFromTransientLengthTrackbar(transientLengthMaxTrackBar.Value);
            transientLengthValue.Text = "" + minValue + "-" + maxValue;
        }

        // from trackbar value to real value
        private int valueFromTransientLengthTrackbar(int value) {
            return Math.Max((int)Math.Round(400.0 * (1.0 - (Math.Log10(1000.0 - value) / 3.0))), 1);
        }

        // from real value to trackbar value
        private int valueToTransientLengthTrackbar(int value) {
            return (int)Math.Round(1000.0 - Math.Pow(10.0, (1 - value/400.0)*3));
        }

        private void takeCareOfTransientsCheckBox_CheckedChanged(object sender, EventArgs e) {
            takeCareOfTransientsPanel.Visible = takeCareOfTransientsCheckBox.Checked;
        }

        private void shiftRightGapToTransientCheckBox_CheckedChanged(object sender, EventArgs e) {
            shiftRightGapToTransientPanel.Visible = shiftRightGapToTransientCheckBox.Checked;
        }

        private void resetToDefaultsButton_Click(object sender, EventArgs e) {
            ReadIniFileAndSetDefaultValues();
        }

        private void mainForm_FormClosing(object sender, FormClosingEventArgs e) {
            MessageBox.Show("Closing", "Closing", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
        }

        private void okButton_Click(object sender, EventArgs e) {
            // validate parameters & exit if everything is ok
            // saves parameters & continues processing
            if (validate())
                exitApplication(true, true);
            }

        private void saveButton_Click(object sender, EventArgs e) {
            // validate parameters & exit if everything is ok
            // saves parameters but does not continue processing
            if (validate())
                exitApplication(true, false);
        }

        private void cancelButton_Click(object sender, EventArgs e) {
            // exit without saving parameters and without processing
            exitApplication(false, false);
        }

        private void infoBox_Click(object sender, EventArgs e) {
            InfoForm infoFrm = new InfoForm();
            infoFrm.ShowDialog();
        }

        // saveChanges: true to save parameters to ini file
        // process: true to return code zero so that python script process the audio
        private void exitApplication(bool saveChanges, bool process) {
            if (saveChanges) WriteIniFile();

            Environment.Exit(process ? 0 : 1);
        }

        private bool validate() {
            // !! only tracks containing transients needs to be validated
            // !! all the other have min/max values properly set
            if (tracksContainingTransientsListBox.CheckedItems.Count == 0) {
                advancedTabControl.SelectedIndex = 1;
                MessageBox.Show("You must select at least one track for 'Tracks containing transients'", "Problem...");
                return false;
            }

            return true;
        }

        private void ReadIniFileAndSetInitialValues() {
            ReadIniFileAndSetValues("settings", false);
        }

        private void ReadIniFileAndSetDefaultValues() {
            ReadIniFileAndSetValues("defaults", true);
        }

        private void ReadIniFileAndSetValues(string section, bool advancedOptionsOnly) {
            IConfigSource source = new IniConfigSource(iniFile);
            if (source == null) return;
            source.Alias.AddAlias("On", true);
            source.Alias.AddAlias("on", true);
            source.Alias.AddAlias("True", true);
            source.Alias.AddAlias("true", true);
            source.Alias.AddAlias("1", true);
            source.Alias.AddAlias("Off", false);
            source.Alias.AddAlias("off", false);
            source.Alias.AddAlias("False", false);
            source.Alias.AddAlias("false", false);
            source.Alias.AddAlias("0", false);

            IConfig cfg = source.Configs[section];
            if (cfg == null) return;

            int itemsToProcess = advancedOptionsOnly ? -1 : cfg.GetInt("itemstoprocess");
            int shiftLeftGapMsec = cfg.GetInt("shiftleftgapmsec");
            int gapFillCrossfadeMsec = cfg.GetInt("gapfillcrossfademsec");
            int crossfadeType = cfg.GetInt("crossfadetype");
            int maxGapMsec = cfg.GetInt("maxgapmsec");
            double minRate = cfg.GetDouble("minrate");
            bool groupHandling = cfg.GetBoolean("grouphandling");
            bool takeCareOfTransients = cfg.GetBoolean("takecareoftransients");
            int transientLengthMinMsec = cfg.GetInt("transientlengthminmsec");
            int transientLengthMaxMsec = cfg.GetInt("transientlengthmaxmsec");
            int transientSplitCrossfadeMsec = cfg.GetInt("transientsplitcrossfademsec");
            bool shiftRightGapToTransient = cfg.GetBoolean("shiftrightgaptotransient");
            int maxGapToTransientLengthMsec = cfg.GetInt("maxgaptotransientlengthmsec");
            int gapToTransientPadMsec = cfg.GetInt("gaptotransientpadmsec");
            string tracksContainingTransients = cfg.Get("trackscontainingtransients");
            bool warnOnGroupMismatch = cfg.GetBoolean("warnongroupmismatch", true);
            bool warnOnTransientStretched = cfg.GetBoolean("warnontransientstretched", true);
            bool warnOnLimitReached = cfg.GetBoolean("warnonlimitreached", true);

            SetValues(itemsToProcess, shiftLeftGapMsec, gapFillCrossfadeMsec, crossfadeType, maxGapMsec, minRate, groupHandling, takeCareOfTransients, transientLengthMinMsec, transientLengthMaxMsec, transientSplitCrossfadeMsec, shiftRightGapToTransient, maxGapToTransientLengthMsec, gapToTransientPadMsec, tracksContainingTransients, warnOnGroupMismatch, warnOnTransientStretched, warnOnLimitReached);
        }

        private void WriteIniFile() {
            IConfigSource source = new IniConfigSource(iniFile);
            if (source == null) return;
            source.Alias.AddAlias("On", true);
            source.Alias.AddAlias("on", true);
            source.Alias.AddAlias("True", true);
            source.Alias.AddAlias("true", true);
            source.Alias.AddAlias("1", true);
            source.Alias.AddAlias("Off", false);
            source.Alias.AddAlias("off", false);
            source.Alias.AddAlias("False", false);
            source.Alias.AddAlias("false", false);
            source.Alias.AddAlias("0", false);

            string section = "settings";
            IConfig cfg = source.Configs[section];
            if (cfg == null) return;

            int itemsToProcess = itemsToProcessCombo.SelectedIndex + 1;
            int shiftLeftGapMsec = Convert.ToInt32(shiftLeftGapMsecNumeric.Value);
            int gapFillCrossfadeMsec = Convert.ToInt32(gapFillCrossfadeMsecNumeric.Value);
            int crossfadeType = crossfadeTypeCombo.SelectedIndex;
            int maxGapMsec = Convert.ToInt32(maxGapMsecNumeric.Value);
            double minRate = valueFromRateTrackbar(minRateTrackBar.Value);
            bool groupHandling = groupHandlingCheckBox.Checked;
            bool takeCareOfTransients = takeCareOfTransientsCheckBox.Checked;
            int transientLengthMinMsec = valueFromTransientLengthTrackbar(transientLengthMinTrackBar.Value);
            int transientLengthMaxMsec = valueFromTransientLengthTrackbar(transientLengthMaxTrackBar.Value);
            int transientSplitCrossfadeMsec = Convert.ToInt32(transientSplitCrossfadeMsecNumeric.Value);
            bool shiftRightGapToTransient = shiftRightGapToTransientCheckBox.Checked;
            int maxGapToTransientLengthMsec = Convert.ToInt32(maxGapToTransientLengthMsecNumeric.Value);
            int gapToTransientPadMsec = Convert.ToInt32(gapToTransientPadMsecNumeric.Value);
            StringBuilder tracksContainingTransients = new StringBuilder("");
            if (tracks != null) {
                for (int i=0; i<tracks.Length; ++i)
                    if (tracksContainingTransientsListBox.GetItemChecked(i)) {
                        tracksContainingTransients.Append(i + 1);
                        tracksContainingTransients.Append("|");
                    }
                if (tracksContainingTransients.Length > 0)
                    tracksContainingTransients.Remove(tracksContainingTransients.Length - 1, 1);
            }
            bool warnOnGroupMismatch = groupMismatchCheckBox.Checked;
            bool warnOnTransientStretched = transientStretchedCheckBox.Checked;
            bool warnOnLimitReached = limitReachedCheckBox.Checked;

            cfg.Set("itemstoprocess", itemsToProcess);
            cfg.Set("shiftleftgapmsec", shiftLeftGapMsec);
            cfg.Set("gapfillcrossfademsec", gapFillCrossfadeMsec);
            cfg.Set("crossfadetype", crossfadeType);
            cfg.Set("maxgapmsec", maxGapMsec);
            cfg.Set("minrate", minRate);
            cfg.Set("grouphandling", groupHandling);
            cfg.Set("takecareoftransients", takeCareOfTransients);
            cfg.Set("transientlengthminmsec", transientLengthMinMsec);
            cfg.Set("transientlengthmaxmsec", transientLengthMaxMsec);
            cfg.Set("transientsplitcrossfademsec", transientSplitCrossfadeMsec);
            cfg.Set("shiftrightgaptotransient", shiftRightGapToTransient);
            cfg.Set("maxgaptotransientlengthmsec", maxGapToTransientLengthMsec);
            cfg.Set("gaptotransientpadmsec", gapToTransientPadMsec);
            cfg.Set("trackscontainingtransients", tracksContainingTransients.ToString());
            cfg.Set("warnongroupmismatch", warnOnGroupMismatch);
            cfg.Set("warnontransientstretched", warnOnTransientStretched);
            cfg.Set("warnonlimitreached", warnOnLimitReached);

            source.Save();
        }

    }
}